package com.zyf.service.video;

import cn.hutool.core.io.FileUtil;
import com.zyf.service.audio.HandleAudioService;
import com.zyf.tts.service.HandleTTSService;
import com.zyf.utils.FFmpegUtils;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FrameGrabber;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 视频处理类
 */
public class HandleVideoService {

    private static final double RATE = 0.002;

    /**
     * 合成视频和字幕
     * // 使用FFmpeg命令行生成视频字幕
     * ffmpeg -i  zimu.mp4   -filter_complex "subtitles=zimu.srt" -c:a copy  zimu_final.mp4
     * <p>
     * 01.Name 风格(Style)的名称. 区分大小写. 不能包含逗号.
     * 02.Fontname        使用的字体名称, 区分大小写.
     * 03.Fontsize        字体的字号
     * 04.PrimaryColour    设置主要颜色, 为蓝-绿-红三色的十六进制代码相排列, BBGGRR. 为字幕填充颜色
     * 05.SecondaryColour  设置次要颜色, 为蓝-绿-红三色的十六进制代码相排列, BBGGRR. 在卡拉OK效果中由次要颜色变为主要颜色.
     * 06.OutlineColour    设置轮廓颜色, 为蓝-绿-红三色的十六进制代码相排列, BBGGRR.
     * 07.BackColour      设置阴影颜色, 为蓝-绿-红三色的十六进制代码相排列, BBGGRR. ASS的这些字段还包含了alpha通道信息. (AABBGGRR), 注ASS的颜色代码要在前面加上&H
     * 08.Bold            -1为粗体, 0为常规
     * 09.Italic          -1为斜体, 0为常规
     * 10.Underline      [-1 或者 0] 下划线
     * 11.Strikeout      [-1 或者 0] 中划线/删除线
     * 12.ScaleX          修改文字的宽度. 为百分数
     * 13.ScaleY          修改文字的高度. 为百分数
     * 14.Spacing        文字间的额外间隙. 为像素数
     * 15.Angle          按Z轴进行旋转的度数, 原点由alignment进行了定义. 可以为小数
     * 16.BorderStyle    1=边框+阴影, 3=纯色背景. 当值为3时, 文字下方为轮廓颜色的背景, 最下方为阴影颜色背景.
     * 17.Outline        当BorderStyle为1时, 该值定义文字轮廓宽度, 为像素数, 常见有0, 1, 2, 3, 4.
     * 18.Shadow          当BorderStyle为1时, 该值定义阴影的深度, 为像素数, 常见有0, 1, 2, 3, 4.
     * 19.Alignment      定义字幕的位置. 字幕在下方时, 1=左对齐, 2=居中, 3=右对齐. 1, 2, 3加上4后字幕出现在屏幕上方. 1, 2, 3加上8后字幕出现在屏幕中间. 例: 11=屏幕中间右对齐. Alignment对于ASS字幕而言, 字幕的位置与小键盘数字对应的位置相同.
     * 左 中 右
     * 上  5  6  7
     * 中  9 10 11
     * 下  1  2  3
     * <p>
     * 20.MarginL        字幕可出现区域与左边缘的距离, 为像素数
     * 21.MarginR        字幕可出现区域与右边缘的距离, 为像素数
     * 22.MarginV        垂直距离
     *
     * @return
     * @throws Exception
     */
    public boolean batchMergeVediosBySrt(String vediopath) throws Exception {
        List<File> srtList = FileUtil.loopFiles(vediopath, file -> file.isFile() && file.getName().endsWith(".srt"));
        List<File> mp4List = FileUtil.loopFiles(vediopath, file -> file.isFile() && file.getName().endsWith(".mp4"));
        String videofile = mp4List.get(0).getAbsolutePath();
        String subtitleFile = srtList.get(0).getAbsolutePath();
        subtitleFile = subtitleFile.replace("\\", "\\\\").replace(":", "\\:");
        String videoFinalfile = vediopath + File.separator + "zimu_final.mp4";
        File file = new File(videoFinalfile);
        if (file.exists()) {
            file.delete();
        }
        try {
            List<String> commands = new ArrayList<>();
            commands.add("ffmpeg");
            commands.add("-i");
            commands.add(videofile);
            commands.add("-filter_complex");
            commands.add("\"subtitles='" + subtitleFile + "':force_style='Alignment=2,MarginV=5,Fontsize=10'\"");
            commands.add("-c:a");
            commands.add("copy");
            commands.add(videoFinalfile);

            String res=FFmpegUtils.execFFmpegCommand(commands);
            System.out.println(res);
        } catch (Exception e) {
            throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
        }
        return true;
    }

    /**
     * 合成单个视频和字幕
     *
     * @param videofile
     * @return
     * @throws Exception
     */
    public boolean mergeVediosBySrt(String videofile) throws Exception {
        File mp4file = new File(videofile);
        String parentpath = mp4file.getParent();
        List<File> audioList = FileUtil.loopFiles(parentpath, file -> file.isFile() && file.getName().endsWith(".srt"));
        if (audioList == null || audioList.size() <= 0) {
            throw new RuntimeException("字幕文件不存在");
        }
        String subtitleFile = audioList.get(0).getAbsolutePath();
        subtitleFile = subtitleFile.replace("\\", "\\\\").replace(":", "\\:");
        String videoFinalfile = videofile.replace(".mp4", "_zimufinal.mp4");
        try {
            List<String> commands = new ArrayList<>();
            commands.add("ffmpeg");
            commands.add("-i");
            commands.add(videofile);
            commands.add("-filter_complex");
            commands.add("\"subtitles='" + subtitleFile + "':force_style='Alignment=2,MarginV=5,Fontsize=10'\"");
            commands.add("-c:a");
            commands.add("copy");
            commands.add(videoFinalfile);

            String res=FFmpegUtils.execFFmpegCommand(commands);
            System.out.println(res);
        } catch (Exception e) {
            throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
        }
        return true;
    }

    /**
     * 合成单个视频和字幕
     *
     * @param videofile
     * @return
     * @throws Exception
     */
    public boolean mergeVediosAndSrtByDoubleFile(String videofile,String subtitleFile) throws Exception {
        subtitleFile = subtitleFile.replace("\\", "\\\\").replace(":", "\\:");
        String videoFinalfile = videofile.replace(".mp4", "_zimufinal.mp4");
        try {
            List<String> commands = new ArrayList<>();
            commands.add("ffmpeg");
            commands.add("-i");
            commands.add(videofile);
            commands.add("-filter_complex");
            commands.add("\"subtitles='" + subtitleFile + "':force_style='Alignment=2,MarginV=5,Fontsize=10'\"");
            commands.add("-c:a");
            commands.add("copy");
            commands.add(videoFinalfile);

            String res=FFmpegUtils.execFFmpegCommand(commands);
            System.out.println(res);
        } catch (Exception e) {
            throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
        }
        return true;
    }

    /**
     * 合并视频和音频,以长度短的为基准
     * <p>
     * ffmpeg -i input_video.mp4 -i input_audio.mp3 -c:v copy -c:a aac -strict experimental -b:a 192k output_video.mp4
     *
     * @throws Exception
     */
    public String mergeVediosAndAudio(String videoPath, String audioPath) throws Exception {
        String videoFinalfile = videoPath.replace(".mp4", "_final.mp4");
        File file = new File(videoFinalfile);
        if (file.exists()) {
            file.delete();
        }
        FFmpegFrameGrabber grabberVideo = null;
        FFmpegFrameGrabber grabberAudio = null;
        try {
            //抓取视频帧
            grabberVideo = new FFmpegFrameGrabber(videoPath);
            //抓取音频帧
            grabberAudio = new FFmpegFrameGrabber(audioPath);
            grabberVideo.start();
            grabberAudio.start();
            //获取时长,以短的文件为主进行截取
            long durationVideo = grabberVideo.getFormatContext().duration() / 1000000;
            long durationAudio = grabberAudio.getFormatContext().duration() / 1000000;
            if (durationVideo != durationAudio) {
                if (durationVideo > durationAudio) {//音频短则以音频长度为主
                    //截取视频
                    videoPath = new VedioUtils().cutVideoByDurationAndStartZ(videoPath, durationAudio);
                } else {
                    //截取音频
                    audioPath = new HandleAudioService().cutAudioByStart(audioPath, durationVideo, "wav");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (grabberVideo != null) {
                grabberVideo.stop();
                grabberVideo.release();
            }
            if (grabberAudio != null) {
                grabberAudio.stop();
                grabberAudio.release();
            }
        }
        try {
            List<String> commands = new ArrayList<>();
            commands.add("ffmpeg");
            commands.add("-i");
            commands.add(videoPath);
            commands.add("-i");
            commands.add(audioPath);
            commands.add("-c:v");
            commands.add("copy");
            commands.add("-c:a");
            commands.add("aac");
            commands.add("-strict");
            commands.add("experimental");
            commands.add("-b:a");
            commands.add("192k");
            commands.add(videoFinalfile);

            String res=FFmpegUtils.execFFmpegCommand(commands);
            System.out.println(res);
        } catch (Exception e) {
            throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
        }
        return videoFinalfile;
    }

    /**
     * 通过ffmpeg将单张图片合成视频
     * ffmpeg -loop 1 -i img1.jpg -c:v libx264 -t 1 -pix_fmt yuv420p output.mp4
     * ffmpeg -loop 1 -i input_image.jpg -c:v libx264 -t 10 -r 24 -pix_fmt yuv420p output_video.mp4
     * "scale=1920:1080"
     * ffmpeg -framerate 24 -i image%03d.jpg -vf "scale=1920:1080" -c:v libx264 -profile:v high -crf 20 -pix_fmt yuv420p output.mp4
     *
     * @throws Exception
     */
    public String mergeSingleImgToVideoByFFmpeg(String imgpath, int duration, int width, int height) throws Exception {
        File file = new File(imgpath);
        String videoFinalfile = file.getParent() + File.separator + System.currentTimeMillis() + ".mp4";
        try {
            List<String> commands = new ArrayList<>();
            commands.add("ffmpeg");
            commands.add("-loop");
            commands.add("1");
            commands.add("-i");
            commands.add(imgpath);
            commands.add("-vf");
            commands.add("\"scale=" + width + ":" + height + ",setdar=dar=9/16\"");
            commands.add("-c:v");
            commands.add("libx264");
            commands.add("-t");
            commands.add("" + duration);
            commands.add("-crf");
            commands.add("20");
            commands.add("-r");
            commands.add("30");
            commands.add("-pix_fmt");
            commands.add("yuv420p");
            commands.add(videoFinalfile);

            String res=FFmpegUtils.execFFmpegCommand(commands);
            System.out.println(res);
        } catch (Exception e) {
            throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
        }
        return videoFinalfile;
    }

    /**
     * 将第二个视频嵌入第一个视频
     * ffmpeg -i 1.mp4 -i 2.mp4 -filter_complex "[0:v][1:v] overlay=(W-w)/2:(H-h)/2" -c:a copy output.mp4
     */
    public String embedDoubleMp4ByDuration(String firstPath, String secondPath) {
        String finalPath = secondPath.replace(".mp4", "_embed.mp4");
        File embedFile = new File(finalPath);
        if (embedFile.exists()) {
            embedFile.delete();
        }
        try {
            List<String> commands = new ArrayList<>();
            commands.add("ffmpeg");
            commands.add("-i");
            commands.add(firstPath);
            commands.add("-i");
            commands.add(secondPath);
            commands.add("-filter_complex");
            commands.add("\"[0:v][1:v] overlay=(W-w)/2:(H-h)/2\"");
            commands.add("-c:a");
            commands.add("copy ");
            commands.add(finalPath);

            String res=FFmpegUtils.execFFmpegCommand(commands);
            System.out.println(res);
        } catch (Exception e) {
            throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
        }

        return finalPath;
    }

    /**
     * 批量一键生成图签视频
     */
    public void batchMakeShortVideos(String folderPath) throws Exception {
        //先批量生成配音文件
        new HandleTTSService().handleBatchTTS(folderPath);
        //筛选文件夹
        File[] handDataList = new File(folderPath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory();
            }
        });
        //遍历文件夹批量处理
        List<String> tips = new ArrayList<>();
        for (File file : handDataList) {
            try {
                String fielfolderPath = file.getAbsolutePath();
                List<File> backList = FileUtil.loopFiles(fielfolderPath, backfile -> backfile.isFile() && (backfile.getName().endsWith(".mp3") || backfile.getName().endsWith(".wav")) && backfile.getName().contains("back"));
                List<File> srcList = FileUtil.loopFiles(fielfolderPath, srcfile -> srcfile.isFile() && (srcfile.getName().endsWith(".mp3") || srcfile.getName().endsWith(".wav")) && !srcfile.getName().contains("back"));
                new MakeVideoService().makeVideoByTag(srcList.get(0).getAbsolutePath(), backList.get(0).getAbsolutePath(), file.getAbsolutePath());
            } catch (Exception e) {
                tips.add(e.toString());
            }
        }
    }

    /**
     * 批量一键生成图签视频
     */
    public void singleMakeShortVideos(String fielfolderPath) throws Exception {
        //先批量生成配音文件
        new HandleTTSService().handleBatchTTS(fielfolderPath);
        //再生成视频
        List<String> tips = new ArrayList<>();
        try {
            List<File> backList = FileUtil.loopFiles(fielfolderPath, backfile -> backfile.isFile() && (backfile.getName().endsWith(".mp3") || backfile.getName().endsWith(".wav")) && backfile.getName().contains("back"));
            List<File> srcList = FileUtil.loopFiles(fielfolderPath, srcfile -> srcfile.isFile() && (srcfile.getName().endsWith(".mp3") || srcfile.getName().endsWith(".wav")) && !srcfile.getName().contains("back"));
            new MakeVideoService().makeVideoByTag(srcList.get(0).getAbsolutePath(), backList.get(0).getAbsolutePath(), fielfolderPath);
        } catch (Exception e) {
            tips.add(e.toString());
        }
        if (tips.size() > 0) {
            throw new RuntimeException(tips.toString());
        }
    }

    /**
     * 合并多个不同的视频
     *ffmpeg -f concat -safe 0 -i file.txt -c:v libx264 -crf 20 -s 720X1280 -preset veryfast output.mp4
     */
    public void mergeMulVideos(String videoFolderPath,String ratio) throws Exception {
        List<File> mp4list = FileUtil.loopFiles(videoFolderPath, backfile -> backfile.isFile() && backfile.getName().endsWith(".mp4"));
        StringBuffer buffer=new StringBuffer();
        for(File file:mp4list){
            buffer.append("file");
            buffer.append(" ");
            buffer.append("'");
            buffer.append(file.getAbsolutePath());
            buffer.append("'");
            buffer.append("\n");
        }
        //保存txt
        String finalpath=videoFolderPath+File.separator+"file.txt";
        String finalMp4Path=videoFolderPath+File.separator+"merged.mp4";
        //写入文件
        FileWriter writer = null;
        try {
            writer = new FileWriter(finalpath, false);
            writer.write(buffer.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (writer != null) {
                writer.flush();
                writer.close();
            }
        }
        List<String> commands = new ArrayList<>();
        commands.add("ffmpeg");
        commands.add("-f");
        commands.add("concat");
        commands.add("-safe");
        commands.add("0");
        commands.add("-i");
        commands.add(finalpath);
        commands.add("-c:v");
        commands.add("libx264");
        commands.add("-crf");
        commands.add("20");
        commands.add("-s");
        ratio=ratio.toUpperCase();
//        commands.add("720X1280");
        commands.add(ratio);
        commands.add("-preset");
        commands.add("veryfast");
        commands.add(finalMp4Path);

        String res=FFmpegUtils.execFFmpegCommand(commands);
        System.out.println(res);


    }


    /**
     * 批量合成多个视频和字幕
     * @param folderPath
     */
    public void batchMergeVediosAndSrtByMulFolder(String folderPath) {
        //筛选文件夹
        File[] handDataList = new File(folderPath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory();
            }
        });
        //遍历文件夹批量处理
        List<String> tips = new ArrayList<>();
        for (File file : handDataList) {
            try {
                String fielfolderPath = file.getAbsolutePath();
                new HandleVideoService().batchMergeVediosBySrt(fielfolderPath);
            } catch (Exception e) {
                tips.add(e.toString());
            }
        }
    }

    /**
     * 根据帧率划分视频
     */
    public void handleVideoByFrameRate(String videoFolderPath){
        //筛选文件
        List<File> mp4list = FileUtil.loopFiles(videoFolderPath, backfile -> backfile.isFile() && backfile.getName().endsWith(".mp4"));
        //遍历文件夹批量处理
        List<String> tips = new ArrayList<>();
        Map<String,List<String>> resMap=new HashMap<>();
        for (File file : mp4list) {
            FrameGrabber grabber = null;
            try {
                //抓取视频帧
                grabber = new FFmpegFrameGrabber(file.getAbsolutePath());
                grabber.start();
                String rate = Math.round(grabber.getFrameRate())+"";
                if(resMap.get(rate)==null){
                    List<String> paths=new ArrayList<>();
                    paths.add(file.getAbsolutePath());
                    resMap.put(rate,paths);
                }else{
                    List<String> exitpaths=resMap.get(rate);
                    exitpaths.add(file.getAbsolutePath());
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (grabber != null) {
                        grabber.stop();
                        grabber.release();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        //分别放到不同的文件夹
        if(resMap.size()>0){
            for(String key:resMap.keySet()){
                File file=new File(videoFolderPath+File.separator+key);
                if(!file.exists()){
                    file.mkdir();
                }
                List<String> videoList=resMap.get(key);
                for(String str:videoList){
                    File videoFile=new File(str);
                    //文件移动
                    Path source = Paths.get(str);
                    Path target = Paths.get(file.getAbsolutePath()+File.separator+videoFile.getName());
                    try {
                        // 移动文件
                        Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
                        System.out.println("文件移动成功！");
                    } catch (IOException e) {
                        System.err.println("文件移动失败：" + e.getMessage());
                    }
                }
            }
        }
    }


    /**
     * 延长视频到指定长度
     * @param videopath
     * @param duation
     */
    public String extendVideo(String videopath, String duation) throws Exception {
        //获取视频原始时长
        FFmpegFrameGrabber grabberVideo = null;
        int durationVideo=0;
        int total=Integer.valueOf(duation);
        try {
            //抓取视频帧
            grabberVideo = new FFmpegFrameGrabber(videopath);
            grabberVideo.start();
            //获取时长,以短的文件为主进行截取
             String resdura = grabberVideo.getFormatContext().duration() / 1000000+"";

            durationVideo=  Integer.valueOf(resdura);

        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            grabberVideo.release();
            grabberVideo.stop();
        }
        int circle=total/durationVideo+1;
        String finalMp4Path=videopath.replace(".mp4","_final.mp4");

        List<String> commands = new ArrayList<>();
        commands.add("ffmpeg");
        commands.add("-stream_loop");
        commands.add(circle+"");
        commands.add("-i");
        commands.add(videopath);
        commands.add("-c");
        commands.add("copy");
        commands.add(finalMp4Path);

        String res=FFmpegUtils.execFFmpegCommand(commands);
        System.out.println(res);
        //视频生成完毕后进行截取
        //将5分钟转化为时分秒
        int hours=total/3600;
        int minus=(total%3600)/60;
        int second=total-hours*3600-minus*60;
        StringBuffer buffer=new StringBuffer();
        if(hours<10){
            buffer.append("0"+hours);
        }else{
            buffer.append(""+hours);
        }
        buffer.append(":");
        if(minus<10){
            buffer.append("0"+minus);
        }else{
            buffer.append(""+minus);
        }
        buffer.append(":");
        if(second<10){
            buffer.append("0"+second);
        }else{
            buffer.append(""+second);
        }

        String finalPath=new VedioUtils().cutVideoByFFmpeg(finalMp4Path, "00:00:00",buffer.toString());
        File oldFile=new File(finalMp4Path);
        if(oldFile.exists()){
            oldFile.delete();
        }
        return finalPath;
    }

    /**
     * 调整视频分辨率
     */
    public String updateVideoRate(String filePath,int width,int height) throws Exception {
       String finla_path=filePath.replace(".mp4","_scaleFinal.mp4");
        List<String> commands = new ArrayList<>();
        commands.add("ffmpeg");
        commands.add("-i");
        commands.add(filePath);
        commands.add("-vf");
        StringBuffer buffer=new StringBuffer("\"scale=");
        if(width==0&&height==0){
            throw  new RuntimeException("宽和高不能同时为0");
        }
        if(width>0&&height>0){
            buffer.append(width+":"+height);
        }
        if(width==0){
            commands.add("-1:"+height);
        }
        if(height==0){
            commands.add(width+":-1");
        }
        buffer.append("\"");
        commands.add(buffer.toString());
        commands.add(finla_path);

        String res=FFmpegUtils.execFFmpegCommand(commands);
        System.out.println(res);

        return finla_path;
    }
}
